from os import path
import json
import functools
from typing import Union

from web3 import Web3
from web3.eth import Contract

from coin.exchange.uniswap_base.types import AddressLike, Address
from coin.exchange.uniswap_base.exceptions import InvalidToken


def str_to_addr(s: Union[AddressLike, str]) -> Address:
  """Idempotent"""
  if isinstance(s, str):
    if s.startswith("0x"):
      return Address(bytes.fromhex(s[2:]))
    else:
      raise Exception(f"Couldn't convert string '{s}' to AddressLike")
  else:
    return s


def addr_to_str(a: AddressLike) -> str:
  if isinstance(a, bytes):
    # Address or ChecksumAddress
    addr: str = Web3.toChecksumAddress("0x" + bytes(a).hex())
    return addr
  elif isinstance(a, str) and a.startswith("0x"):
    addr = Web3.toChecksumAddress(a)
    return addr

  raise InvalidToken(a)


def is_same_address(a1: Union[AddressLike, str], a2: Union[AddressLike, str]) -> bool:
  return str_to_addr(a1) == str_to_addr(a2)


def validate_address(a: AddressLike) -> None:
  assert addr_to_str(a)


def load_abi(path: path) -> str:
  with open(path) as f:
    abi: str = json.load(f)
  return abi


@functools.lru_cache()
def load_contract(w3: Web3, abi_path: path, address: AddressLike) -> Contract:
  address = Web3.to_checksum_address(address)
  return w3.eth.contract(address=address, abi=load_abi(abi_path))


def load_contract_erc20(w3: Web3, address: AddressLike) -> Contract:
  return load_contract(w3, "python/coin/exchange/uniswap_base/abi/erc20.abi", address)
